package com.twu.biblioteca.view.console;

import com.google.common.collect.Maps;
import com.google.inject.Inject;
import com.google.inject.Singleton;

import com.twu.biblioteca.controller.Observer;
import com.twu.biblioteca.entity.Book;
import com.twu.biblioteca.entity.Movie;
import com.twu.biblioteca.entity.User;
import com.twu.biblioteca.enums.ConsoleEvent;
import com.twu.biblioteca.enums.MapKey;
import com.twu.biblioteca.enums.Status;
import com.twu.biblioteca.enums.ViewState;
import com.twu.biblioteca.util.ConsoleReader;
import com.twu.biblioteca.util.ConsoleWriter;
import com.twu.biblioteca.view.command.Command;
import com.twu.biblioteca.view.console.ui_element.Menu;
import com.twu.biblioteca.view.console.ui_element.MenuMap;

import java.util.List;
import java.util.Map;

@Singleton
public class ConsoleViewController implements Observer {
  private final ViewResolverMap viewResolverMap;
  private final MenuMap menuMap;

  private ConsoleReader reader;
  private ConsoleWriter writer;
  private List<Book> bookList;
  private List<Movie> movieList;
  private String userLibraryNumber;
  private Map<Command.Type, ProcessedCommandHandler> processCommandHandlerMap;

  @Inject
  public ConsoleViewController(ViewResolverMap viewResolverMap, MenuMap menuMap) {
    this.viewResolverMap = viewResolverMap;
    this.menuMap = menuMap;
  }

  @Inject
  public void setReader(ConsoleReader reader) {
    this.reader = reader;
  }

  @Inject
  public void setWriter(ConsoleWriter writer) {
    this.writer = writer;
  }

  public void start() {
    processCommandHandlerMap = getProcessCommandHandlerMap();
    handleConsoleEvent(ConsoleEvent.DisplayMessage, "Welcome to Biblioteca");
    handleConsoleEvent(ConsoleEvent.Login, null);
  }

  public void handleConsoleEvent(ConsoleEvent consoleEvent, Object data) {
    switch (consoleEvent) {
      case Login:
        Menu loginMenu = this.menuMap.getMenu(ConsoleEvent.Login);
        loginMenu.select(this, 1);
        break;

      case DisplayMessage:
        Map<MapKey, Object> model = Maps.newHashMap();
        model.put(MapKey.Message, (String) data);
        display(ViewState.Welcome, model);
        break;

      case MainMenu:
        Menu mainMenu = this.menuMap.getMenu(ConsoleEvent.MainMenu);
        Map<MapKey, Object> mainMenuModel = Maps.newHashMap();
        mainMenuModel.put(MapKey.Menus, mainMenu);
        display(ViewState.Menu, mainMenuModel);
        String errorMessage = "Enter a number to select a menu item [1 to %d]: ";
        int item = reader.readLineAndConvertInt(mainMenu.size(), errorMessage);
        mainMenu.select(this, item);
        break;
    }
  }

  public void display(ViewState state, Map<MapKey, Object> model) {
    String messageAtConsole = viewResolverMap.getMessageAtConsole(state, model);
    writer.display(messageAtConsole);
  }

  public Book getBookToReturn(String title) {
    for (Book b : this.bookList) {
      if (b.getTitle().equalsIgnoreCase(title)) {
        return b;
      }
    }
    return null;
  }

  public Movie getMovieToReturn(String title) {
    for (Movie m : this.movieList) {
      if (m.getTitle().equalsIgnoreCase(title)) {
        return m;
      }
    }
    return null;
  }

  public Book getBookToCheckout(int index) {
    return this.bookList.get(index - 1);
  }

  public Movie getMovieToCheckout(int index) {
    return this.movieList.get(index - 1);
  }

  public int getAvailableBookSize() {
    return this.bookList.size();
  }

  public int getAvailableMovieSize() {
    return this.movieList.size();
  }

  public String getUserLibraryNumber() {
    return userLibraryNumber;
  }

  @Override
  public void notify(Map<MapKey, Object> objectMap) {
    Command.Type processedCommand = (Command.Type) objectMap.get(MapKey.Command);
    processCommandHandlerMap.get(processedCommand).handle(objectMap);
  }

  private Map<Command.Type, ProcessedCommandHandler> getProcessCommandHandlerMap() {
    Map<Command.Type, ProcessedCommandHandler> map = Maps.newHashMap();
    map.put(Command.Type.BookList, new BookCommandHandler());
    map.put(Command.Type.ReturnBook, new ReturnBookCommandHandler());
    map.put(Command.Type.CheckOutBook, new CheckOutBookCommandHandler());

    map.put(Command.Type.MovieList, new MovieCommandHandler());
    map.put(Command.Type.ReturnMovie, new ReturnMovieCommandHandler());
    map.put(Command.Type.CheckOutMovie, new CheckOutMovieCommandHandler());

    map.put(Command.Type.Login, new LoginCommandHandler());
    map.put(Command.Type.UserInfo, new UserInfoCommandHandler());
    return map;
  }

  private interface ProcessedCommandHandler {
    void handle(Map<MapKey, Object> objectMap);
  }


  private class BookCommandHandler implements ProcessedCommandHandler {
    protected void moreAction(Map<MapKey, Object> objectMap) {}

    @Override
    public void handle(Map<MapKey, Object> objectMap) {
      display(ViewState.BookList, objectMap);
      moreAction(objectMap);
      ConsoleViewController.this.bookList = (List<Book>) objectMap.get(MapKey.Books);
      handleConsoleEvent(ConsoleEvent.MainMenu, null);
    }
  }


  private class ReturnBookCommandHandler extends BookCommandHandler {
    @Override
    protected void moreAction(Map<MapKey, Object> objectMap) {
      Status returnBookStatus = (Status) objectMap.get(MapKey.Status);
      String title = (String) objectMap.get(MapKey.ITEM_TITLE);

      String message = returnBookStatus == Status.Success
          ? String.format("Thank you for returning the book, %s", title)
          : "That is not a valid book to return.";
      handleConsoleEvent(ConsoleEvent.DisplayMessage, message);
    }
  }


  private class CheckOutBookCommandHandler extends BookCommandHandler {
    @Override
    protected void moreAction(Map<MapKey, Object> objectMap) {
      Status checkOutStatus = (Status) objectMap.get(MapKey.Status);
      String title = (String) objectMap.get(MapKey.ITEM_TITLE);

      String message = checkOutStatus == Status.Success
          ? String.format("Thank you! Enjoy the book, %s.", title)
          : "That book is not available.";
      handleConsoleEvent(ConsoleEvent.DisplayMessage, message);
    }
  }


  private class MovieCommandHandler implements ProcessedCommandHandler {
    protected void moreAction(Map<MapKey, Object> objectMap) {}

    @Override
    public void handle(Map<MapKey, Object> objectMap) {
      display(ViewState.MovieList, objectMap);
      moreAction(objectMap);
      ConsoleViewController.this.movieList = (List<Movie>) objectMap.get(MapKey.Movies);
      handleConsoleEvent(ConsoleEvent.MainMenu, null);
    }
  }


  private class ReturnMovieCommandHandler extends MovieCommandHandler {
    @Override
    protected void moreAction(Map<MapKey, Object> objectMap) {
      Status returnBookStatus = (Status) objectMap.get(MapKey.Status);

      String title = (String) objectMap.get(MapKey.ITEM_TITLE);
      String message = returnBookStatus == Status.Success
          ? String.format("Thank you for returning the movie, %s.", title)
          : "That is not a valid movie to return.";
      handleConsoleEvent(ConsoleEvent.DisplayMessage, message);
    }
  }


  private class CheckOutMovieCommandHandler extends MovieCommandHandler {
    @Override
    protected void moreAction(Map<MapKey, Object> objectMap) {
      Status checkOutStatus = (Status) objectMap.get(MapKey.Status);
      String title = (String) objectMap.get(MapKey.ITEM_TITLE);
      String message = checkOutStatus == Status.Success
          ? String.format("Thank you! Enjoy the movie, %s", title)
          : "That movie is not available.";
      handleConsoleEvent(ConsoleEvent.DisplayMessage, message);
    }
  }


  private class LoginCommandHandler implements ProcessedCommandHandler {
    @Override
    public void handle(Map<MapKey, Object> objectMap) {
      Status status = (Status) objectMap.get(MapKey.Status);

      if (status == Status.Success) {
        User user = (User) objectMap.get(MapKey.User);
        ConsoleViewController.this.userLibraryNumber = user.getLibraryNumber();
        handleConsoleEvent(ConsoleEvent.MainMenu, null);
      } else {
        String message = "Login Failed! Invalid Id or Password";
        handleConsoleEvent(ConsoleEvent.DisplayMessage, message);
        handleConsoleEvent(ConsoleEvent.Login, null);
      }
    }
  }


  private class UserInfoCommandHandler implements ProcessedCommandHandler {
    @Override
    public void handle(Map<MapKey, Object> objectMap) {
      display(ViewState.UserInfo, objectMap);
      handleConsoleEvent(ConsoleEvent.MainMenu, null);
    }
  }
}
